#include <string>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <ctime>
using namespace std;

#include <unistd.h>
#include <dirent.h>
#include <libgen.h>
#include <magic.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <libexif/exif-data.h>

int g_notify_fd(-1);

bool
initialize(const char* path)
{
	if ( g_notify_fd != -1 ) close(g_notify_fd);

	if ( -1 == (g_notify_fd = inotify_init()) ) return false;

	int wd;
	if ( -1 == (wd = inotify_add_watch(g_notify_fd, path, IN_MOVED_TO|IN_MOVED_FROM|IN_CLOSE_WRITE|IN_DELETE|IN_CREATE|IN_MODIFY)) ) return false;

	return true;
}

string
printMask(uint32_t mask)
{
	string e;
	if ( mask & IN_ACCESS ) e += "ACCESS ";
	if ( mask & IN_ATTRIB ) e += "ATTRIB ";
	if ( mask & IN_CLOSE_WRITE ) e += "CLOSE_WRITE ";
	if ( mask & IN_CLOSE_NOWRITE ) e += "CLOSE_NOWRITE ";
	if ( mask & IN_CREATE ) e += "CREATE ";
	if ( mask & IN_DELETE ) e += "DELETE ";
	if ( mask & IN_DELETE_SELF ) e += "DELETE_SELF ";
	if ( mask & IN_MODIFY ) e += "MODIFY ";
	if ( mask & IN_MOVE_SELF ) e += "MOVE_SELF ";
	if ( mask & IN_MOVED_FROM ) e += "MOVED_FROM ";
	if ( mask & IN_MOVED_TO ) e += "MOVED_TO ";
	if ( mask & IN_OPEN ) e += "OPEN ";

	return e;
}

string
getDate(const char* fn)
{
	ExifData* ed(NULL);
	ExifEntry* ee(NULL);
	string ret;

	if ( NULL == (ed = exif_data_new_from_file(fn) ) )
	{
		cerr << "ERROR: no exif: " << fn << endl;
		return ret;
	}

	do
	{
		if ( NULL == ( ee = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_DATE_TIME)) )
		{
			cerr << "ERROR: no exif of date: " << fn << endl;
			break;
		}

		char buf[1024];
		exif_entry_get_value(ee, buf, sizeof(buf));
		ret = buf;
		ret[4] = ret[7] = '-';
	} while (false);

	exif_data_unref(ed);

	return ret;
}

const char*
getMIME(const char* file)
{
	static bool init(false);
	static magic_t mt;
	if ( !init )
	{
		init = true;
		mt = magic_open(MAGIC_CONTINUE|MAGIC_ERROR|MAGIC_MIME);
		magic_load(mt, NULL);
	}

	return magic_file(mt, file);
}

void
scanDir(const char* path, const char* dest)
{
	DIR* dir(opendir(path));
	struct dirent* ent;

	if ( NULL == dir )
	{
		cerr << "ERROR: failed to scan: " << path << endl;
		return;
	}

	while ( NULL != ( ent = readdir(dir) ) )
	{
		if ( ent->d_type == DT_REG )
		{
			string final(path);
			final += '/';
			final += ent->d_name;

			if ( !strncasecmp("image/jpeg", getMIME(final.c_str()), 10) )
			{
				string ts( getDate(final.c_str()) );
				cerr << "TIME: " << ts << endl;

				struct tm tm;
				if ( NULL != ( strptime(ts.c_str(), "%Y-%m-%d %H:%M:%S", &tm)) )
				{
					char target_name[1024];
					const char* final_base(strrchr(final.c_str(), '/')+1);
					snprintf(target_name, sizeof(target_name), "%s/%4d-%02d-%02d/%s", dest, tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, final_base);
					char* target_base(strdup(target_name));
					mkdir(dirname(target_base), 0777);
					free(target_base);

					cout << "Move: " << final << " ---> " << target_name << endl;

					rename(final.c_str(), target_name);
				}
				else
				{
					cout << "failed strptime: " << ts << endl;
				}
			}
		}
		else if ( ent->d_type == DT_DIR )
		{
			if ( !strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") )
			{
				continue;
			}

			string final(path);
			final += '/';
			final += ent->d_name;
			scanDir(final.c_str(), dest);
		}
	}

	closedir(dir);
}

void
dispatch(const char* basedir, const char* destdir)
{
	struct inotify_event ev[1024];
	ssize_t rsize;

	do
	{
		fprintf(stderr, "-1-\n");
		rsize = read(g_notify_fd, (char*)&ev, sizeof(ev));
		fprintf(stderr, "-2-\n");
		if ( rsize != -1 )
		{
			scanDir(basedir, destdir);
		}
		else
		{
			cout << strerror(errno) << endl;
		}
	} while(true);
}

void
help(void)
{
	cout << "dirmon [OPTIONS...]" << endl;
	cout << "OPTIONS..." << endl;
	cout << "	-d		Daemon mode." << endl;
	cout << "	-b [BASEDIR] 	Base directory for watching." << endl;
	cout << "	-t [TARGETDIR] 	Target directory for renaming." << endl;
	cout << "	-h 		Help screen" << endl;
}

int
main(int argc, char* argv[])
{
	int opt;
	bool opt_daemon(false);
	string opt_basedir("./watchdir");
	string opt_destdir("./destdir");

	while ( (opt = getopt(argc, argv, "db:t:h") ) != -1 )
	{
		switch(opt)
		{
		case 'd': opt_daemon = true; break;
		case 'b': opt_basedir = optarg; break;
		case 't': opt_destdir = optarg; break;
		case 'h': help(); exit(EXIT_FAILURE);
		}
	}

	if ( opt_daemon )
	{
		if ( -1 == daemon(0, 0) )
		{
			cout << "ERROR: " << strerror(errno) << endl;
			return EXIT_FAILURE;
		}
	}

	if ( !initialize(opt_basedir.c_str()) )
	{
		cout << "ERROR: " << strerror(errno) << endl;
		return EXIT_FAILURE;
	}

	scanDir(opt_basedir.c_str(), opt_destdir.c_str());

	dispatch(opt_basedir.c_str(), opt_destdir.c_str());
}

